package jlib.component.core
{
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.IBitmapDrawable;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	
	import jlib.core.ICloneable;
	import jlib.utils.MatrixUtil;
	import jlib.utils.RectangleUtil;
	import jlib.utils.TransformUtil;
	
	/**
	 * 컴포넌트의 백그라운드 구성을 이루는 객체.
	 * @author Park Jong Hee
	 */	
	public class Background extends Shape implements ICloneable
	{
		static private const INIT_MAT:Matrix = new Matrix();
		static private const UNION_RECT:Rectangle = new Rectangle();
		
		private var m_width:Number;
		private var m_height:Number;
		private var m_scale9Grid:Rectangle;
		
		private var m_source:*;
		private var m_bitmapData:BitmapData;
		private var m_round:int;
		
		private var m_bChangedSize:Boolean;
		protected var m_bRendered:Boolean;
		
		private var m_bSmooth:Boolean;
		/**
		 * Background 생성자. 
		 * @param $source Bitmap, uint, DisplayObject 객체를 배경으로 지정할 수 있다.
		 * @param $round Background의 Rounding 을 준다.
		 */		
		public function Background($source:*=0x00ffffff, $round:int=0, $smooth:Boolean=false): void {
			m_width = m_height = 1;
			m_bSmooth = $smooth;
			setSource($source), setRound($round);
			addEventListener(Event.ADDED_TO_STAGE, onAdded );
			addEventListener(Event.REMOVED_FROM_STAGE, onRemoved );
		}
		
		protected function onAdded(event:Event):void {
			stage.addEventListener(Event.RENDER, onRender );
			stage.invalidate();
		}
		
		protected function onRemoved(event:Event):void {
			stage.removeEventListener(Event.RENDER, onRender );
			stage.invalidate();
			m_bRendered = false;
		}
		
		/**
		 * 사각형 Background에  Rounding 을 준다.
		 * @param $round round 값.
		 */		
		public function setRound($round:int): void {
			m_round = $round;
			if(stage) stage.invalidate();
		}
		/**
		 * Bitmap, uint, DisplayObject 객체를 배경으로 지정할 수 있다.
		 * DisplayObject 는 Bitmap 객체로 Bitmap Draw가 되기 때문에 참조 객체와 동기 되진 않는다. 
		 * @param $source Bitmap, uint, DisplayObject 객체
		 */		
		public function setSource($source:*): void { 
			if($source == null) return ;
			if(m_source is Number && m_source == $source) return ;
			m_source = $source;
			if(m_source is Number) {
				if(m_bChangedSize) setBitmapData(new BitmapData(m_width, m_height, true, m_source ));
				else {
					if(m_bitmapData == null) setBitmapData(new BitmapData(m_width, m_height, true, m_source ));
					UNION_RECT.width = m_width; 
					UNION_RECT.height = m_height;
					m_bitmapData.lock();
					m_bitmapData.fillRect(UNION_RECT, m_source);
					m_bitmapData.unlock();
				}
			}else if(m_source is IBitmapDrawable){
				m_source = $source;
				if(m_bitmapData == null) setBitmapData(new BitmapData(m_source.width, m_source.height, true, 0x00000000));
				if(m_source.width != m_width || m_source.height != m_height || m_bChangedSize) {
					setBitmapData(new BitmapData(m_source.width, m_source.height, true, 0x00000000));
				}
				m_bitmapData.lock();
				m_bitmapData.draw(m_source, null, null, null, null, m_bSmooth);
				m_bitmapData.unlock();
				m_width = m_bitmapData.width;
				m_height = m_bitmapData.height;
			}
			if(stage) stage.invalidate();
		}
		
		public function update($event:Event=null): void {
			onRender($event);
		}
		
		/**
		 * Background 객체를 update한다.
		 * @param e Event 객체.
		 */		
		protected function onRender(event:Event):void {
			if(!m_bitmapData) return ;
			if(m_width <= 0 && m_height <= 0) return ;
			graphics.clear();
			graphics.beginBitmapFill(m_bitmapData, INIT_MAT, false, m_bSmooth);
			graphics.drawRoundRect(0, 0, m_width, m_height, m_round, m_round);
			m_bRendered = true;
		}
		
		override public function get scale9Grid() : Rectangle { return m_scale9Grid; }
		override public function set scale9Grid(r:Rectangle) : void  {
			if ((m_scale9Grid == null && r != null) || (m_scale9Grid != null && !m_scale9Grid.equals(r))) {
				if (r == null) {
					var currentWidth:Number = width;
					var currentHeight:Number = height;
					m_scale9Grid = null;
					setBitmapData(m_source.clone());
					setSize(currentWidth, currentHeight);
				} else {
					if (!validGrid(r)) {
						throw (new Error("영역의 범위가 넘어갔습니다."));
						return;
					}
					m_scale9Grid = r.clone();
					resizeBitmap(width, height);
					scaleX = 1, scaleY = 1;
				}
			}
		}
		
		override public function get width(): Number { return m_width; }
		override public function set width(value:Number): void {
			m_bChangedSize = false;
			if (value != m_width) {
				m_width = value;
				m_bChangedSize = true;
				setSize(value, m_height);
			}
		}
		
		override public function get height(): Number { return m_height; }
		override public function set height(value:Number): void {
			m_bChangedSize = false;
			if (value != m_height) {
				m_height = value;
				m_bChangedSize = true;
				setSize(m_width, value);
			}
		}
		
		public function hit($x:Number, $y:Number): Boolean {
			UNION_RECT.width = m_width; 
			UNION_RECT.height = m_height;
			return RectangleUtil.containsPoint(UNION_RECT, $x, $y, transform.concatenatedMatrix);
		}
		
		override public function getBounds(targetCoordinateSpace:DisplayObject):Rectangle {
			if(m_bRendered){
				return super.getBounds(targetCoordinateSpace);
			}
			UNION_RECT.x = x;
			UNION_RECT.y = y;
			UNION_RECT.width = m_width; 
			UNION_RECT.height = m_height;
			var mat:Matrix;
			if(targetCoordinateSpace) mat = targetCoordinateSpace.transform.matrix;
			else mat = INIT_MAT;
			return TransformUtil.transformRect(UNION_RECT, mat);
		}
		
		override public function getRect(targetCoordinateSpace:DisplayObject):Rectangle {
			if(m_bRendered){
				return super.getRect(targetCoordinateSpace);
			}
			UNION_RECT.x = x;
			UNION_RECT.y = y;
			UNION_RECT.width = m_width; 
			UNION_RECT.height = m_height;
			var mat:Matrix;
			if(targetCoordinateSpace) mat = targetCoordinateSpace.transform.matrix;
			else mat = INIT_MAT;
			return TransformUtil.transformRect(UNION_RECT, mat);
		}
		
		private function setSize(w:Number, h:Number) : void {
			if (m_scale9Grid == null) {
				m_width = w;
				m_height = h;
			} else {
				w = Math.max(w, m_source.width - m_scale9Grid.width);
				h = Math.max(h, m_source.height - m_scale9Grid.height);
				resizeBitmap(w, h);
			}
			if(stage) stage.invalidate();
		}
		
		private function resizeBitmap(w:Number, h:Number) : void  {
			var bmpData:BitmapData = new BitmapData(w, h, true, 0x00000000);
			var rows:Array = [0, m_scale9Grid.top, m_scale9Grid.bottom, m_source.height];
			var cols:Array = [0, m_scale9Grid.left, m_scale9Grid.right, m_source.width];
			var dRows:Array = [0, m_scale9Grid.top, h - (m_source.height - m_scale9Grid.bottom), h];
			var dCols:Array = [0, m_scale9Grid.left, w - (m_source.width - m_scale9Grid.right), w];
			
			var origin:Rectangle;
			var draw:Rectangle;
			var mat:Matrix = new Matrix();
			for (var cx:int=0;cx<3;cx++) {
				for (var cy:int=0;cy<3;cy++) {
					origin = new Rectangle(cols[cx], rows[cy], cols[cx + 1] - cols[cx], rows[cy + 1] - rows[cy]);
					draw = new Rectangle(dCols[cx], dRows[cy], dCols[cx + 1] - dCols[cx], dRows[cy + 1] - dRows[cy]);
					mat.identity();
					mat.a = draw.width / origin.width;
					mat.d = draw.height / origin.height;
					mat.tx = draw.x - origin.x * mat.a;
					mat.ty = draw.y - origin.y * mat.d;
					bmpData.draw(m_source, mat, null, null, draw, true);
				}
			}
			setBitmapData(bmpData);
		}
		
		internal function getBitmapData(): BitmapData { return m_bitmapData; }
		internal function setBitmapData($bmp:BitmapData) : void  {
			if(m_bitmapData == null) {
				m_bitmapData = $bmp;
				return ;
			}
			m_bitmapData.dispose();
			m_bitmapData = $bmp;
		}
		
		private function validGrid($r:Rectangle) : Boolean  { 
			trace($r.right, $r.bottom, m_source.width, m_source.height);
			return $r.right <= m_source.width && $r.bottom <= m_source.height; 
		}
		
		public function dealloc(): void {
			m_source = null;
			m_bRendered = false;
			if(m_bitmapData){
				m_bitmapData.dispose();
				m_bitmapData = null;
			}
			removeEventListener(Event.ADDED_TO_STAGE, onAdded );
			removeEventListener(Event.REMOVED_FROM_STAGE, onRemoved );
		}

		public function clone():Object {
			var result:Background = new Background(getBitmapData().clone(), m_round, m_bSmooth);
			result.scale9Grid = scale9Grid;
			return result;
		}
		
		public function toBitmapData():BitmapData {
			if(!m_bitmapData) return null;
			return m_bitmapData.clone();
		}
		
	}//c
}